package net.simflow.swf.analyzer.network.opt;

import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;

import net.simflow.swf.analyzer.network.data.Flow;
import net.simflow.swf.analyzer.network.data.Instance;
import net.simflow.swf.analyzer.network.data.Node;
import net.simflow.swf.analyzer.network.data.ProcessorConfig;
import net.simflow.swf.analyzer.network.data.Service;
import uk.org.taverna.scufl2.api.configurations.Configuration;
import uk.org.taverna.scufl2.api.container.WorkflowBundle;
import uk.org.taverna.scufl2.api.core.BlockingControlLink;
import uk.org.taverna.scufl2.api.core.ControlLink;
import uk.org.taverna.scufl2.api.core.DataLink;
import uk.org.taverna.scufl2.api.core.Processor;
import uk.org.taverna.scufl2.api.core.Workflow;
import uk.org.taverna.scufl2.api.port.InputProcessorPort;
import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;

public class FlowBuilder {

	private static FlowBuilder instance = new FlowBuilder();

	private FlowBuilder() {
	}

	public static FlowBuilder getInstance() {
		return instance;
	}

	public Flow build(WorkflowBundle workflowBundle) {
		try {
			if (workflowBundle == null) {
				return null;
			}

			/* Create nodes */

			HashMap<Processor, ProcessorConfig> pm = ProcessorMapExtracter
					.getInstance().extract(workflowBundle);
			HashSet<InputWorkflowPort> ips = InputPortSetExtracter
					.getInstance().extract(workflowBundle);
			HashSet<OutputWorkflowPort> ops = OutputPortSetExtracter
					.getInstance().extract(workflowBundle);

			Workflow mwf = workflowBundle.getMainWorkflow();

			Flow f = new Flow(mwf.getName(), mwf.getWorkflowIdentifier());
			HashMap<Processor, Node> pnm = new HashMap<Processor, Node>();
			HashMap<InputWorkflowPort, Node> ipnm = new HashMap<InputWorkflowPort, Node>();
			HashMap<String, Node> ipnameNM = new HashMap<String, Node>();
			HashMap<OutputWorkflowPort, Node> opnm = new HashMap<OutputWorkflowPort, Node>();
			HashMap<String, Node> opnameNM = new HashMap<String, Node>();
			HashMap<URI, Service> usm = new HashMap<URI, Service>();

			for (Entry<Processor, ProcessorConfig> en : pm.entrySet()) {
				Instance ins = Instance.makeInstance(en.getKey(),
						en.getValue(), mwf);
				Service svc;
				if (ins.getWSDLURI() == null) {
					svc = new Service(ins);
				} else {
					svc = usm.get(ins.getWSDLURI());
					if (svc == null) {
						svc = new Service(ins);
						usm.put(ins.getWSDLURI(), svc);
					} else {
						svc.addInstance(ins);
					}
				}
				Node n = new Node(svc);

				f.addInstance(ins);
				f.addService(svc);
				f.addNode(n);
				pnm.put(en.getKey(), n);
			}

			for (InputWorkflowPort ip : ips) {
				Instance ins = Instance.makeInstance(ip, mwf);
				Service svc = new Service(ins);
				Node n = new Node(svc);

				f.addInstance(ins);
				f.addService(svc);
				f.addNode(n);
				ipnm.put(ip, n);
				ipnameNM.put(PortNameMaker.getInstance().make(ip), n);
			}

			for (OutputWorkflowPort op : ops) {
				Instance ins = Instance.makeInstance(op, mwf);
				Service svc = new Service(ins);
				Node n = new Node(svc);

				f.addInstance(ins);
				f.addService(svc);
				f.addNode(n);
				opnm.put(op, n);
				opnameNM.put(PortNameMaker.getInstance().make(op), n);
			}

			/* Connect nodes */
			HashSet<Node> nestedWorkflowSet = new HashSet<Node>();
			for (Workflow fl : workflowBundle.getWorkflows()) {
				for (DataLink link : fl.getDataLinks()) {
					Node sender = null;
					Node receiver = null;
					OutputProcessorPort senderOutputPort = null;
					InputProcessorPort receiverInputPort = null;
					if (link.getReceivesFrom().getClass() == OutputProcessorPort.class) {
						senderOutputPort = ((OutputProcessorPort) link
								.getReceivesFrom());
						sender = pnm.get(senderOutputPort.getParent());
					} else if (link.getReceivesFrom().getClass() == InputWorkflowPort.class) {
						sender = ipnm.get((InputWorkflowPort) link
								.getReceivesFrom());
					}
					if (link.getSendsTo().getClass() == InputProcessorPort.class) {
						receiverInputPort = ((InputProcessorPort) link
								.getSendsTo());
						receiver = pnm.get(receiverInputPort.getParent());
					} else if (link.getSendsTo().getClass() == OutputWorkflowPort.class) {
						receiver = opnm.get((OutputWorkflowPort) link
								.getSendsTo());
					}
					if (sender != null && receiver != null) {
						if (senderOutputPort != null) {
							if (sender
									.getService()
									.getType()
									.equals(new URI(
											"http://ns.taverna.org.uk/2010/activity/nested-workflow"))) {
								nestedWorkflowSet.add(sender);
								String port = PortNameMaker.getInstance().make(
										pm.get(senderOutputPort.getParent())
												.getConfiguration(),
										senderOutputPort);

								if (port != null) {
									sender = opnameNM.get(port);
								}
							}
						}

						if (receiver != null && receiver != null) {
							if (receiverInputPort != null) {
								if (receiver
										.getService()
										.getType()
										.equals(new URI(
												"http://ns.taverna.org.uk/2010/activity/nested-workflow"))) {
									nestedWorkflowSet.add(receiver);
									String port = PortNameMaker.getInstance()
											.make(pm.get(
													receiverInputPort
															.getParent())
													.getConfiguration(),
													receiverInputPort);

									if (port != null) {
										receiver = ipnameNM.get(port);
									}
								}
							}

							if (receiver != null && sender != null
									&& receiver != sender) {
								sender.addChildData(receiver);
								receiver.addParentData(sender);
							}
						}
					}
				}

				HashMap<Processor, Node> wfnOutputM = new HashMap<Processor, Node>();
				HashMap<Processor, Node> wfnInputM = new HashMap<Processor, Node>();
				for (ControlLink link : fl.getControlLinks()) {
					BlockingControlLink bcl = null;
					if (link.getClass().equals(BlockingControlLink.class)) {
						bcl = (BlockingControlLink) link;
					} else {
						continue;
					}
					Processor senderProcessor = bcl.getUntilFinished();
					Processor receiverProcessor = bcl.getBlock();
					Node sender = pnm.get(senderProcessor);
					Node receiver = pnm.get(receiverProcessor);
					if (sender
							.getService()
							.getType()
							.equals(new URI(
									"http://ns.taverna.org.uk/2010/activity/nested-workflow"))) {
						nestedWorkflowSet.add(sender);
						Node newSender = wfnOutputM.get(senderProcessor);
						if (newSender == null) {
							newSender = new Node(new Service(sender
									.getService().getInstanceSet()));
							wfnOutputM.put(senderProcessor, newSender);
							f.addNode(newSender);
							f.addService(newSender.getService());
							Configuration config = pm.get(senderProcessor)
									.getConfiguration();
							for (OutputWorkflowPort port : workflowBundle
									.getWorkflows()
									.getByName(
											PortNameMaker.getInstance()
													.makeWorkflowName(config))
									.getOutputPorts()) {
								String portName = PortNameMaker.getInstance()
										.make(port);
								if (portName != null) {
									Node op = opnameNM.get(portName);
									op.addChildData(newSender);
									newSender.addParentData(op);
								}
							}
						}
						sender = newSender;
					}

					if (receiver
							.getService()
							.getType()
							.equals(new URI(
									"http://ns.taverna.org.uk/2010/activity/nested-workflow"))) {
						nestedWorkflowSet.add(receiver);
						Node newReceiver = wfnInputM.get(receiverProcessor);
						if (newReceiver == null) {
							newReceiver = new Node(new Service(receiver
									.getService().getInstanceSet()));
							wfnInputM.put(receiverProcessor, newReceiver);
							f.addNode(newReceiver);
							f.addService(newReceiver.getService());
							Configuration config = pm.get(receiverProcessor)
									.getConfiguration();
							for (InputWorkflowPort port : workflowBundle
									.getWorkflows()
									.getByName(
											PortNameMaker.getInstance()
													.makeWorkflowName(config))
									.getInputPorts()) {
								String portName = PortNameMaker.getInstance()
										.make(port);
								if (portName != null) {
									Node ip = ipnameNM.get(portName);
									newReceiver.addChildData(ip);
									ip.addParentData(newReceiver);
								}
							}
						}
						receiver = newReceiver;
					}

					if (sender != null && receiver != null) {
						sender.addChildControl(receiver);
						receiver.addParentControl(sender);
					}
				}
			}

			/* Search workflow acnodes */
			for (Node n : f.getNodeSet()) {
				if (n.getService()
						.getType()
						.equals(new URI(
								"http://ns.taverna.org.uk/2010/activity/nested-workflow"))) {
					if (n.getLinkCount() == 0) {
						nestedWorkflowSet.add(n);
					}
				}
			}

			/* Remove nested workflow nodes */
			for (Node n : nestedWorkflowSet) {
				f.removeNode(n);
				f.getServiceSet().remove(n.getService());
				f.getInstanceSet().removeAll(n.getService().getInstanceSet());
			}

			/* Extract entrances and exits */
			for (Node n : f.getNodeSet()) {
				if (n.getParentDataSet().isEmpty()
						&& n.getParentControlSet().isEmpty()) {
					f.addEntrance(n);
				}
				if (n.getChildDataSet().isEmpty()
						&& n.getChildControlSet().isEmpty()) {
					f.addExit(n);
				}
			}

			return f;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}
